package com.mate.cloud.enums.mvc;

import com.google.common.collect.Maps;
import com.mate.cloud.config.BaseEnumProperties;
import com.mate.cloud.enums.CommonEnum;
import lombok.Cleanup;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 自动注册CommonEnum
 *
 * @author cong zhi
 * @email 448341911@qq.com
 * @createDate 2023/3/3 14:38
 * @version: v1.0
 */
@Slf4j
@Component
public class CommonEnumRegistry {

    private static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";

    private static final String BASE_ENUM_CLASS_NAME = CommonEnum.class.getName();


    @Getter
    private final Map<String, List<CommonEnum>> nameDict = Maps.newLinkedHashMap();

    @Getter
    private final Map<Class, List<CommonEnum>> classDict = Maps.newLinkedHashMap();

    private BaseEnumProperties baseEnumProperties;


    @Autowired
    private ResourceLoader resourceLoader;

    /**
     * @param baseEnumProperties {@link com.mate.cloud.config.BaseEnumProperties}
     */
    public CommonEnumRegistry(@NonNull BaseEnumProperties baseEnumProperties) {
        this.baseEnumProperties = baseEnumProperties;
    }

    /**
     * <p>
     * 1、首先通过 Spring 的 ResourcePatternResolver 根据配置的 basePackage 对classpath进行扫描
     * 2、扫描结果以Resource来表示，通过 MetadataReader 读取 Resource 信息，并将其解析为 ClassMetadata
     * 3、获得 ClassMetadata 之后，找出实现 CommonEnum 的类
     * 4将 CommonEnum 实现类注册到两个 Map 中进行缓存
     *
     * </p>
     */
    @PostConstruct
    public void initDict() {
        String basePackage = baseEnumProperties.getBasePackage();
        if (StringUtils.isEmpty(basePackage)) {
            return;
        }
        ResourcePatternResolver resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(this.resourceLoader);
        MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
        try {
            String pkg = toPackage(basePackage);
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + pkg + DEFAULT_RESOURCE_PATTERN;
            Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                        ClassMetadata classMetadata = metadataReader.getClassMetadata();

                        String[] interfaceNames = classMetadata.getInterfaceNames();
                        // 实现 BASE_ENUM_CLASS_NAME 接口
                        if (Arrays.asList(interfaceNames).contains(BASE_ENUM_CLASS_NAME)) {
                            String className = classMetadata.getClassName();

                            // 加载class
                            Class<?> aClass = Class.forName(className);
                            if (aClass.isEnum() && CommonEnum.class.isAssignableFrom(aClass)) {
                                Object[] enumConstants = aClass.getEnumConstants();
                                List<CommonEnum> commonEnums = Arrays.asList(enumConstants).stream()
                                        .filter(e -> e instanceof CommonEnum)
                                        .map(e -> (CommonEnum) e).collect(Collectors.toList());
                                String key = convertKeyFromClassName(aClass.getSimpleName());

                                this.nameDict.put(key, commonEnums);
                                this.classDict.put(aClass, commonEnums);
                            }
                        }

                    } catch (Throwable ex) {
                        // ignore
                    }
                }
            }
        } catch (IOException e) {
            log.error("failed to load dict by auto register", e);
        }
    }

    private String convertKeyFromClassName(String className) {
        return Character.toLowerCase(className.charAt(0)) + className.substring(1);
    }

    private String toPackage(String basePackage) {
        String result = basePackage.replace(".", "/");
        return result + "/";
    }
}
